/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package protect02;

import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Random;

/**
 *
 * @author Constantin
 */
public class RSA {
    
    private static Random rnd = new Random();
    
    private static void println(Object o) {
        System.out.println(o);
    }
    
    public static RSAKeys generateKeys() {
        
        println("\n\nGENERATE KEYS");
        
        BigInteger base = BigInteger.TEN.pow(12);
        
        BigInteger p = base.add(new BigInteger(30, rnd)).nextProbablePrime();
        BigInteger q = base.add(new BigInteger(30, rnd)).nextProbablePrime();
        
        println(String.format("p = %s", p));
        println(String.format("q = %s", q));
        
        BigInteger n = p.multiply(q);
        
        BigInteger psqs = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
        
        EuclidExtended ep = EuclidExtended.generateDualPrimary(n.bitLength() - 1, psqs);
        BigInteger e = ep.b;
        BigInteger d = ep.y;
        
        println(String.format("MOD = %d", ep.d));
        println(String.format("n = %s", n));
        println(String.format("e = %s", e));
        println(String.format("d = %d", d));
        
        BigInteger bitest = d.multiply(e).mod(psqs);
        println(String.format("bitest = %s", bitest));
        
        OpenKey ok = new OpenKey(e, n);
        CloseKey ck = new CloseKey(d);
        
        return new RSAKeys(ok, ck);
    }
    
    public static void encode(InputStream is, OutputStream os, OpenKey key) throws IOException {
        
        println("\n\nENCODE");
        
        BigInteger e = key.getE();
        BigInteger n = key.getN();
        
        byte[] nByte = key.getN().toByteArray();
        
        int k = nByte.length - 1;
        int K = nByte.length;
        
        byte[] zeroBuf = new byte[K];
        byte[] readBuf = new byte[k];
        readBuf[0] = 0;
        int readed = is.read(readBuf, 1, readBuf.length - 1);
        int lastReaded = readed;
        int writed = 0;
        while (readed >= readBuf.length - 1) {
     
            //println(String.format("%d\treaded = %d length = %d", readed, readBuf.length));
            BigInteger parted = new BigInteger(readBuf);
            println(String.format("%d\tparted = %s size = %d", writed, parted, parted.toByteArray().length));
            
            BigInteger coded = parted.modPow(e, n);
            println(String.format("%d\tcoded = %s size = %d", writed, coded, coded.toByteArray().length));
            
            byte[] codeda = coded.toByteArray();
            
            int zeros = K - codeda.length;
            os.write(zeroBuf, 0, zeros);
            //println(String.format("%d\twrited zeros = %d", writed, zeros));
            os.write(codeda);
            //println(String.format("%d\twrited codeda.length = %d", writed, codeda.length));
            
            writed++;
            
            lastReaded = readed;
            readed = is.read(readBuf, 1, readBuf.length - 1);
        }
        if (readed > 0) {
            lastReaded = readed;
            byte dummy = 0;
            Arrays.fill(readBuf, readed + 1, readBuf.length, dummy);
            //println(String.format("filled %d by %d", readBuf.length - readed, dummy));
            
            //println(String.format("readed = %d length = %d", readed, readBuf.length));
            BigInteger parted = new BigInteger(readBuf);
            println(String.format("%d\tparted = %s size = %d", writed, parted, parted.toByteArray().length));
            
            BigInteger coded = parted.modPow(e, n);
            println(String.format("%d\tcoded = %s size = %d", writed, coded, coded.toByteArray().length));
            
            byte[] codeda = coded.toByteArray();
            
            int zeros = K - codeda.length;
            os.write(zeroBuf, 0, zeros);
            //println(String.format("writed zeros = %d", zeros));
            os.write(codeda);
            //println(String.format("writed codeda.length = %d", codeda.length));
        }
        {
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeInt(lastReaded);
            //println(String.format("written = %d, %d", readed, dos.size()));
        }
    }
    
    public static void decode(InputStream is, OutputStream os, OpenKey open, CloseKey close) throws IOException {
        
        println("\n\nDECODE");
        
        BigInteger n = open.getN();
        
        BigInteger d = close.getD();
        
        byte[] nByte = open.getN().toByteArray();
        
        int k = nByte.length - 1;
        int K = nByte.length;
        int ks = k - 1;
        
        byte[] zeroBuf = new byte[K];
        
        byte[] temp = new byte[K];
        is.read(temp);
        
        byte[] temp1 = new byte[K];
        int readed = is.read(temp1);
        int writed = 0;
        while (readed == K) {
            
            BigInteger encoded = new BigInteger(temp);
            //println(String.format("encoded = %s length = %d", encoded, encodeda.length));
            //println(String.format("try to decode %s", encoded));
            BigInteger decoded = encoded.modPow(d, n);
            byte[] decodeda = decoded.toByteArray();
            println(String.format("%d\tdecoded = %s length = %d", writed, decoded, decodeda.length));
            
            int zeros = ks - decodeda.length;
            zeros = zeros < 0 ? 0 : zeros;
            
            int from = decodeda.length > ks ? decodeda.length - ks : 0;
            int to = decodeda.length; 
            int length = ks - zeros;
            
            os.write(zeroBuf, 0, zeros);
            //println(String.format("writed zeros = %d", zeros));
            os.write(decodeda, from, length);
            println(String.format("writed decodeda = %d from = %d to = %d", decodeda.length, from, to));
            
            byte[] tt = temp1;
            temp1 = temp;
            temp = tt;
                      
            readed = is.read(temp1);
            writed++;
        }
        if (readed > 0) {
            DataInputStream dis = new DataInputStream(new ByteArrayInputStream(temp1));
            readed =  dis.readInt();
            //println(String.format("left = %d", readed));
            
            BigInteger encoded = new BigInteger(temp);
            //println(String.format("encoded = %s length = %d", encoded, encodeda.length));
            
            BigInteger decoded = encoded.modPow(d, n);
            byte[] decodeda = decoded.toByteArray();
            println(String.format("%d\tdecoded = %s length = %d", writed, decoded, decodeda.length));
            
            byte[] orig = new byte[ks];
            System.arraycopy(decodeda, decodeda.length - ks, orig, 0, orig.length);
            os.write(orig, 0, readed);
        }
    }
}
